package com.chh.nonstandardjson;

import java.io.IOException;
import java.util.*;

/**
 *
 * 非标准json语法推导，支持key是非字符串类型
 *
 * json -> array
 *        | map
 *
 * array -> [item array_tail]
 *         | []
 *
 * array_tail ->  (,item)+
 *              | null
 *
 * item -> map
 *        | array
 *        | num
 *        | string
 *        | bool
 *
 * map -> {pair map_tail}
 *       | {}
 *
 * pair -> key: item
 *
 * key ->  string |
 *         num
 *
 * map_tail -> (,pair)+
 *             | null
 *
 */

public class JsonParser {
    private final JsonLexer jsonLexer;
    private Token currToken;
    private boolean isEof;

    public JsonParser(JsonLexer jsonLexer) {
        this.jsonLexer = jsonLexer;
    }

    private void nextToken() {
        try {
            this.currToken =  this.jsonLexer.nextToken();
            this.isEof = this.currToken.type == TokenType.EOF;
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private RuntimeException newException() {
        return new RuntimeException("语法解析错误，token:【" + this.currToken + "】");
    }

    public Object parse() {
        nextToken();
        if(this.currToken.type == TokenType.BEGIN_ARRAY) {
            List<Object> array = new ArrayList<>();
            array(array);
            if(this.isEof) {
                return array;
            } else {
                throw newException();
            }
        } else if(this.currToken.type == TokenType.BEGIN_OBJECT) {
            Map<Object, Object> map = new LinkedHashMap<>();
            map(map);
            if(this.isEof) {
                return map;
            } else {
                throw newException();
            }
        } else {
            throw newException();
        }
    }

    private void array(List<Object> array) {
        nextToken();
        if(this.currToken.type == TokenType.END_ARRAY) {
            nextToken();
            return;
        }
        Object item = item();
        array.add(item);

        arrayTail(array);
        nextToken();
    }

    private void arrayTail(List<Object> array) {
        if(this.currToken.type == TokenType.END_ARRAY) {
            return;
        }
        while (true) {
            if (this.currToken.type == TokenType.COMMA) {
                nextToken();
                Object item = item();
                array.add(item);
                if(this.currToken.type == TokenType.END_ARRAY) {
                    return;
                }
            } else {
                throw newException();
            }
        }
    }

    private Object item() {
        Object item = this.currToken.value;
        switch (this.currToken.type) {
            case NULL:
                nextToken();
                break;
            case INTEGER:
                nextToken();
                break;
            case FLOAT:
                nextToken();
                break;
            case BOOLEAN:
                nextToken();
                break;
            case STRING:
                nextToken();
                break;
            case BEGIN_ARRAY:
                List<Object> array1 = new ArrayList<>();
                this.array(array1);
                item = array1;
                break;
            case BEGIN_OBJECT:
                Map<Object, Object> map1 = new LinkedHashMap<>();
                this.map(map1);
                item = map1;
                break;
            default:
                throw newException();
        }
        return item;
    }

    private void map(Map<Object,Object> map){
        nextToken();
        if(isSymbol()) {
            if(this.currToken.type == TokenType.END_OBJECT) {
                nextToken();
            } else {
                throw newException();
            }
        } else {
            pair(map);
            mapTail(map);
            nextToken();
        }
    }

    private boolean isSymbol() {
        TokenType tokenType = this.currToken.type;
        return tokenType == TokenType.BEGIN_OBJECT ||
                tokenType == TokenType.END_OBJECT||
                tokenType == TokenType.BEGIN_ARRAY||
                tokenType == TokenType.END_ARRAY||
                tokenType == TokenType.COLON||
                tokenType == TokenType.COMMA;
    }

    private void pair(Map<Object,Object> map) {
        Object key = this.currToken.value;
        switch (this.currToken.type) {
            case STRING:
                break;
            case INTEGER:
                break;
            case FLOAT:
                break;
            default:
                throw newException();
        }
        nextToken();
        if(this.currToken.type == TokenType.COLON) {
            nextToken();
            Object item = item();
            map.put(key, item);
            return;
        }
        throw newException();
    }

    private void mapTail(Map<Object,Object> map) {
        if(this.currToken.type == TokenType.END_OBJECT) {
            return;
        } else {
            while (true) {
                if(this.currToken.type == TokenType.COMMA) {
                    nextToken();
                    pair(map);
                    if(this.currToken.type == TokenType.END_OBJECT) {
                        return;
                    }
                } else {
                    throw newException();
                }
            }
        }
    }
}
